package work.linruchang.oauthclientproject.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.Cache;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import work.linruchang.oauthclientproject.config.handler.*;
import work.linruchang.oauthclientproject.config.oauth.*;
import work.linruchang.oauthclientproject.util.EhcacheUtil;
import work.linruchang.oauthclientproject.util.EnhanceSpringUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Oauth认证-用户相关接口
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/08/05
 * @since 1.8
 **/
@Controller
@RequestMapping("user-oauth")
@Slf4j
public class UserOauthController4 {

    private final String tokenUrl = "http://localhost:9090/oauth/token";
    private final String oauthUrl = "http://127.0.0.1:9090/oauth/authorize";
    private final String userInfoUrl = "http://localhost:9090/user/user_info";
    private final String clientId = "3fc74ed352484464c9ecbbb40fae4a691f3a6884e43c001f8e40a1abe79f3ad0";
    private final String clientSecret = "a210aa5c21b3c388fc078";

    private final String redirectUri = "http://127.0.0.1:8080/user-oauth/callback";

    /**
     * key：远程IP地址
     * value：state状态值
     */
    // private final ConcurrentHashMap<String, List<String>> cacheAuthCodeStateMap = new ConcurrentHashMap<>();

    private final static ConcurrentHashMap<String, Dict> cacheStateInfoMap = new ConcurrentHashMap<>();

    @GetMapping("oauth-url")
    @ResponseBody
    public Dict getOauthUrl() {
        String state1 = UUID.fastUUID().toString(true);
        String state2 = UUID.fastUUID().toString(true);
        Dict firstOauthParameter = Dict.create()
                .set("client_id", clientId)
                .set("redirect_uri", redirectUri)
                .set("response_type", ResponseType.RESPONSE_TYPE_CODE)
                .set("scope", Scope.SCOPE_USER_INFO)
                .set("state", state1);
        cacheStateInfoMap.put(state1, firstOauthParameter);

        Dict secondOauthParameter = Dict.create()
                .set("client_id", clientId)
                .set("redirect_uri", redirectUri)
                .set("response_type", ResponseType.RESPONSE_TYPE_TOKEN)
                .set("scope", Scope.SCOPE_USER_INFO)
                .set("state", state2);
        cacheStateInfoMap.put(state2, secondOauthParameter);


        return Dict.create()
                .set("code", 200)
                .set("firstOauthUrl", StrUtil.format("{}?{}", oauthUrl, URLUtil.buildQuery(firstOauthParameter, StandardCharsets.UTF_8)))
                .set("secondOauthUrl", StrUtil.format("{}?{}", oauthUrl, URLUtil.buildQuery(secondOauthParameter, StandardCharsets.UTF_8)));
    }


    @GetMapping
    public String indexPage() {
        return StrUtil.format("redirect:{}", "/user-oauth/login");
    }

    /**
     * 登录页
     * 如果已登录则跳到用户信息页面
     * 非则跳到非登录页面
     *
     * @return
     */
    @GetMapping("login")
    public String loginPage() {
        if (EnhanceSpringUtil.getCurrrentRequest().getSession().getAttribute("otherSysUser") != null) {
            return StrUtil.format("redirect:{}", "/user-oauth/user");
        } else {
            return "oauthLogin";
        }
    }


    /**
     * oauth账号密码、或者凭证方式授权登录
     *
     * @param type          oauth登录类型
     * @param oauthUsername 授权服务器账号
     * @param oauthPassword 授权服务器密码
     * @return
     */
    @PostMapping("login")
    public String login(Integer type, String oauthUsername, String oauthPassword, RedirectAttributes redirectAttributes) {
        type = ObjectUtil.defaultIfNull(type, 0);

        if (!(type == 0 && StrUtil.isAllNotBlank(oauthUsername, oauthPassword)) || !StrUtil.equalsAny(String.valueOf(type), "0", "1")) {
            redirectAttributes.addAttribute("error", RequestEnum.ResponseCodeEnum.INVALID_PARAMETERS.getDescription());
            return StrUtil.format("redirect:{}", "/user-oauth/login");
        }
        // Assert.isTrue((type==0 && StrUtil.isAllNotBlank(oauthUsername,oauthPassword)) || (type ==1 && StrUtil.isNotBlank(credentials)), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_PARAMETERS));


        Dict formParam = Dict.create()
                .set("oauthUsername", oauthUsername)
                .set("oauthPassword", oauthPassword)
                .set("grant_type", type == 0 ? GrantType.GRANT_TYPE_PASSWORD : GrantType.GRANT_TYPE_CLIENT_CREDENTIALS)
                .set("scope", type == 0 ? Scope.SCOPE_USER_INFO : Scope.SCOPE_USER_INFOS);
        MapUtil.removeNullValue(formParam);


        String completeTokenUrl = StrUtil.format("{}?{}", tokenUrl, URLUtil.buildQuery(formParam, StandardCharsets.UTF_8));
        HttpRequest tokenHttpRequest = HttpUtil.createPost(completeTokenUrl);
        log.info("获取令牌地址：{}", completeTokenUrl);
        tokenHttpRequest.header("Authorization", StrUtil.format("Basic {}", Base64.encode(StrUtil.format("{}:{}", clientId, clientSecret))));
        HttpResponse tokenHttpResponse = tokenHttpRequest.execute();
        log.info("获取令牌响应结果[{}]：{}", tokenHttpResponse.getStatus(), tokenHttpResponse.body());

        if (tokenHttpResponse.getStatus() == 200 && StrUtil.isBlank(JSONUtil.getByPath(JSONUtil.parse(tokenHttpResponse.body()), "code", null))) {

            HttpServletRequest httpServletRequest = EnhanceSpringUtil.getCurrrentRequest();
            Map<String, String> oauthUrlParameter = EnhanceSpringUtil.getCurrrentRequestRequestParameterOneValueMap();
            oauthUrlParameter.put("response_type", type == 0 ? GrantType.GRANT_TYPE_PASSWORD : GrantType.GRANT_TYPE_CLIENT_CREDENTIALS);

            TokenInfo tokenInfo = JSONUtil.toBean(tokenHttpResponse.body(), TokenInfo.class);
            HttpRequest userInfoHttpRequest = HttpUtil.createGet(userInfoUrl);
            if (StrUtil.equalsAnyIgnoreCase(tokenInfo.getToken_type(), TokenType.token_type_bearer)) {
                userInfoHttpRequest.header("Authorization", StrUtil.format("Bearer {}", tokenInfo.getAccess_token()));
                HttpResponse userInfoHttpResponse = userInfoHttpRequest.execute();
                log.info("获取第三方系统用户信息结果：{}", userInfoHttpResponse.body());

                if (userInfoHttpResponse.getStatus() != 200) {
                    redirectAttributes.addAttribute("error", RequestEnum.ResponseCodeEnum.FAILED_GET_THIRD_USER_INFO.getDescription());
                    return StrUtil.format("redirect:{}", "/user-oauth/login");
                }
                // Assert.isTrue(userInfoHttpResponse.getStatus() == 200, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.FAILED_GET_THIRD_USER_INFO));

                HttpSession session = httpServletRequest.getSession();
                OtherSysUser otherSysUser = JSONUtil.toBean(userInfoHttpResponse.body(), OtherSysUser.class);
                tokenInfo.setOtherSysUser(otherSysUser);
                tokenInfo.setSessionId(session.getId());
                tokenInfo.setOauthUrlParameter(oauthUrlParameter);
                tokenInfo.setCreateTime(DateUtil.date());
                EhcacheUtil.set("tokenInfo", UUID.fastUUID().toString(true), tokenInfo, ObjectUtil.defaultIfNull(tokenInfo.getExpires_in_refresh(), tokenInfo.getExpires_in()) * 1000);

                session.setAttribute("otherSysUser", otherSysUser);
                EnhanceSpringUtil.addCookie(otherSysUser);
                EnhanceSpringUtil.addCookie(tokenInfo);
                EnhanceSpringUtil.addCookie("response_type", oauthUrlParameter.get("response_type"));
            } else {
                redirectAttributes.addAttribute("error", RequestEnum.ResponseCodeEnum.UNSUPPORT_AUTHENTICATION_TYPE.getDescription());
                return StrUtil.format("redirect:{}", "/user-oauth/login");
                // throw new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.UNSUPPORT_AUTHENTICATION_TYPE);
            }
        } else {
            redirectAttributes.addAttribute("error", RequestEnum.ResponseCodeEnum.FAILED_GET_ACCESS_TOKEN.getDescription());
            return StrUtil.format("redirect:{}", "/user-oauth/login");
            // throw new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.FAILED_GET_ACCESS_TOKEN);
        }
        return StrUtil.format("redirect:{}", "/user-oauth/user");
    }


    /**
     * 用户信息页面
     *
     * @return
     */
    @GetMapping("user")
    public String userPage(Model model) {


        // model.addAttribute()
        return "oauthUser";
    }

    /**
     * 退出登录，然后跳到用户oauth登录页面
     *
     * @return
     */
    @GetMapping("logout")
    public String logout() {

        HttpSession session = EnhanceSpringUtil.getCurrrentRequest().getSession();

        // 删除缓存==主要是用于work.linruchang.qq.mybaitsplusjoin.controller.UserOauthController4.getAllTokenInfos
        Serializable cacheId = (Serializable) EhcacheUtil.getAllEntry("tokenInfo").stream()
                .filter(cacheEntry -> {
                    TokenInfo tokenInfo = (TokenInfo) cacheEntry.getValue();
                    return StrUtil.equals(tokenInfo.getSessionId(), session.getId());
                })
                .findFirst()
                .map(Cache.Entry::getKey)
                .orElse(null);
        EhcacheUtil.del("tokenInfo", cacheId);


        session.invalidate();
        HttpServletResponse currentResponse = EnhanceSpringUtil.getCurrentResponse();
        currentResponse.setHeader("Clear-Site-Data", "\"cache\", \"cookies\"");
        return StrUtil.format("redirect:{}", "/user-oauth/login");
    }

    /**
     * oauth回调地址
     *
     * @return
     */
    @GetMapping("callback")
    public String callback(String state, String code, String error, TokenInfo requestTokenInfo, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) {
        log.info("回调地址：{}?{}", httpServletRequest.getRequestURL(), httpServletRequest.getQueryString());
        Assert.isTrue(cacheStateInfoMap.containsKey(state), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_STATE));

        Dict oauthUrlParameter = cacheStateInfoMap.get(state);
        cacheStateInfoMap.remove(state);

        if (StrUtil.isNotBlank(error)) {
            redirectAttributes.addAttribute("error", error);
            return StrUtil.format("redirect:{}", "/user-oauth/login");
        }

        TokenInfo tokenInfo = null;
        if (StrUtil.isNotBlank(code)) {
            Dict formParam = Dict.create()
                    .set("code", code)
                    .set("grant_type", GrantType.GRANT_TYPE_AUTHORIZATION_CODE)
                    .set("redirect_uri", redirectUri);

            String completeTokenUrl = StrUtil.format("{}?{}", tokenUrl, URLUtil.buildQuery(formParam, StandardCharsets.UTF_8));
            HttpRequest tokenHttpRequest = HttpUtil.createPost(completeTokenUrl);
            log.info("获取令牌地址：{}", completeTokenUrl);
            tokenHttpRequest.header("Authorization", StrUtil.format("Basic {}", Base64.encode(StrUtil.format("{}:{}", clientId, clientSecret))));
            HttpResponse tokenHttpResponse = tokenHttpRequest.execute();
            log.info("获取令牌响应结果：{}", tokenHttpResponse.body());

            Assert.isTrue(tokenHttpResponse.isOk(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.FAILED_GET_ACCESS_TOKEN));
            tokenInfo = JSONUtil.toBean(tokenHttpResponse.body(), TokenInfo.class);


        } else {
            tokenInfo = requestTokenInfo;
        }


        HttpRequest userInfoHttpRequest = HttpUtil.createGet(userInfoUrl);
        if (StrUtil.equalsAnyIgnoreCase(tokenInfo.getToken_type(), TokenType.token_type_bearer)) {
            userInfoHttpRequest.header("Authorization", StrUtil.format("Bearer {}", tokenInfo.getAccess_token()));
            HttpResponse userInfoHttpResponse = userInfoHttpRequest.execute();
            log.info("获取第三方系统用户信息结果：{}", userInfoHttpResponse.body());

            Assert.isTrue(userInfoHttpResponse.isOk(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.FAILED_GET_THIRD_USER_INFO));
            HttpSession session = httpServletRequest.getSession();
            OtherSysUser otherSysUser = JSONUtil.toBean(userInfoHttpResponse.body(), OtherSysUser.class);
            tokenInfo.setOtherSysUser(otherSysUser);
            tokenInfo.setSessionId(session.getId());
            tokenInfo.setOauthUrlParameter(oauthUrlParameter);
            tokenInfo.setCreateTime(DateUtil.date());
            EhcacheUtil.set("tokenInfo", UUID.fastUUID().toString(true), tokenInfo, ObjectUtil.defaultIfNull(tokenInfo.getExpires_in_refresh(), tokenInfo.getExpires_in()) * 1000);

            session.setAttribute("otherSysUser", otherSysUser);
            EnhanceSpringUtil.addCookie(otherSysUser);
            EnhanceSpringUtil.addCookie(tokenInfo);
            EnhanceSpringUtil.addCookie("response_type", oauthUrlParameter.getStr("response_type"));
        } else {
            throw new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.UNSUPPORT_AUTHENTICATION_TYPE);
        }
        return StrUtil.format("redirect:{}", "/user-oauth/user");
    }

    @GetMapping("oauth-info-admin")
    public String getOauthInfoAdmin() {
        return "oauthInfoAdmin";
    }

    @GetMapping("all-token-infos")
    @ResponseBody
    public List<TokenInfo> getAllTokenInfos() {
        return EhcacheUtil.getAllValues("tokenInfo", TokenInfo.class);
    }

    /**
     * 退出登录
     * 命令浏览器清除当前站点的所有缓存以及cookie信息
     *
     * @param httpServletResponse
     */
    @GetMapping("basic-logout")
    @SneakyThrows
    public void basicLogout(HttpServletResponse httpServletResponse) {
        httpServletResponse.setStatus(401);
        httpServletResponse.setHeader("Clear-Site-Data", "\"cache\", \"cookies\"");
    }
}
